//################################################################################
// MIT License
// Copyright (c) 2024 ZhangYihua
//
// Change Logs:
// Date           Author       Notes
// 2023-08-03     ZhangYihua   first version
//
// Description  : 
//################################################################################

module watch_dog #(
parameter           TIME_UNIT_SELF          = 1'b1,     // 1'b0:use external time_unit_i; 1'b1:generate time_unit internal;
parameter           TU_PERIOD               = 16384,    // unused if TIME_UNIT_SELF==1'b0, else must set TU_PERIOD = 2^N
parameter           FAIL_LEVEL              = 1'b0,     // if 'watch_level'==FAIL_LEVEL, means fail event is watched
parameter           LEVEL_TU                = 10,       // if fail level in 'watch_level' is watched for a long time(LEVEL_TU*TU_PERIOD), request reset
parameter           FAIL_EDGE               = 1'b0,     // 1'b0:falling edge means fail event is watched; 1'b1:rising edge means fail event
parameter           EDGE_NUM                = 30,       // if enough times of fail edge in 'watch_edge' is watched, request reset
parameter           RST_ACT_LEVEL           = 1'b1,     // gen_reset active 1'b0:low; 1'b1:high;
parameter           RST_ACT_NUM             = 10,       // clock number of gen_reset active
parameter           RST_GAP_TU              = 10        // minimus gap(RST_GAP_TU*TU_PERIOD) between 2 gen_reset active
) ( 
input                                       rst_n,
input                                       clk,

// periodical pulse with longer period
input                                       time_unit_i,    // unused if TIME_UNIT_SELF==1'b1
output                                      time_unit_o,    // cascade out

input                                       watch_level,
input                                       watch_edge,

output  reg                                 gen_reset
);

//################################################################################
// define local varialbe and localparam
//################################################################################
localparam          ACT_BW                  = $clog2(RST_ACT_NUM);
localparam [ACT_BW-1:0]     ACT_MAX         = RST_ACT_NUM-1;

localparam          GAP_BW                  = $clog2(RST_GAP_TU+1);
localparam [GAP_BW-1:0]     GAP_MAX         = RST_GAP_TU;

wire                                        time_unit;
wire                                        level_req;
wire                                        edge_req;
wire                                        rst_req;
reg                 [ACT_BW-1:0]            cnt_act;
wire                                        act_expire;
reg                 [GAP_BW-1:0]            cnt_gap;
wire                                        gap_expire;

//################################################################################
// main
//################################################################################

generate if (TIME_UNIT_SELF==1'b1) begin:G_SELF
    localparam          TIME_BW                 = $clog2(TU_PERIOD);
    reg                 [TIME_BW-1:0]           cnt_time;
    reg                                         time_expire;

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_time <=`U_DLY {TIME_BW{1'b0}};
        end else begin
            cnt_time <=`U_DLY cnt_time + 1'b1;
        end
    end

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            time_expire <=`U_DLY 1'b0;
        end else begin
            time_expire <=`U_DLY (cnt_time=={TIME_BW{1'b1}}) ? 1'b1 : 1'b0;
        end
    end
    
    assign time_unit = time_expire;
end else begin:G_NSELF
    assign time_unit = time_unit_i;
end endgenerate

assign time_unit_o = time_unit;

generate if (LEVEL_TU>0) begin:G_LEVEL
    localparam          LEVEL_BW                = $clog2(LEVEL_TU+1);
    localparam [LEVEL_BW-1:0]   LEVEL_MAX       = LEVEL_TU;
    reg                 [LEVEL_BW-1:0]          cnt_level;

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_level <=`U_DLY LEVEL_MAX;
        end else begin
            if (gap_expire==1'b0 || watch_level!=FAIL_LEVEL) begin
                cnt_level <=`U_DLY LEVEL_MAX;
            end else if (level_req==1'b0 && time_unit==1'b1) begin
                cnt_level <=`U_DLY cnt_level - 1'd1;
            end
        end
    end

    assign level_req = (cnt_level=={LEVEL_BW{1'b0}}) ? 1'b1 : 1'b0;

end else begin:G_NLEVEL

    assign level_req = 1'b0;

end endgenerate


generate if (EDGE_NUM>0) begin:G_EDGE
    localparam          EDGE_BW                 = $clog2(EDGE_NUM+1);
    localparam [EDGE_BW-1:0]    EDGE_MAX        = EDGE_NUM;
    reg                                         edge_1d;
    reg                                         edge_2d;
    reg                 [EDGE_BW-1:0]           cnt_edge;
    
    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            edge_1d <=`U_DLY 1'b1;
            edge_2d <=`U_DLY 1'b1;
        end else begin
            edge_1d <=`U_DLY (FAIL_EDGE==1'b0) ? ~watch_edge : watch_edge;
            edge_2d <=`U_DLY edge_1d;
        end
    end

    always@(posedge clk or negedge rst_n) begin
        if (rst_n==1'b0) begin
            cnt_edge <=`U_DLY EDGE_MAX;
        end else begin
            if (gap_expire==1'b0)
                cnt_edge <=`U_DLY EDGE_MAX;
            else if (edge_2d==1'b0 && edge_1d==1'b1 && edge_req==1'b0)
                cnt_edge <=`U_DLY cnt_edge - 1'd1;
            else
                ;
        end
    end
    assign edge_req = (cnt_edge=={EDGE_BW{1'b0}}) ? 1'b1 : 1'b0;

end else begin:G_NEDGE
    
    assign edge_req = 1'b0;

end endgenerate

assign rst_req = level_req | edge_req;
always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        gen_reset <=`U_DLY RST_ACT_LEVEL;
    end else begin
        if (gen_reset==RST_ACT_LEVEL && act_expire==1'b1)
            gen_reset <=`U_DLY ~RST_ACT_LEVEL;
        else if (gen_reset==(~RST_ACT_LEVEL) && gap_expire==1'b1 && rst_req==1'b1)
            gen_reset <=`U_DLY RST_ACT_LEVEL;
        else
            ;
    end
end

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_act <=`U_DLY ACT_MAX;
    end else begin
        if (gen_reset==(~RST_ACT_LEVEL))
            cnt_act <=`U_DLY ACT_MAX;
        else if (act_expire==1'b0)
            cnt_act <=`U_DLY cnt_act - 1'd1;
        else
            ;
    end
end
assign act_expire = (cnt_act=={ACT_BW{1'b0}}) ? 1'b1 : 1'b0;

always@(posedge clk or negedge rst_n) begin
    if (rst_n==1'b0) begin
        cnt_gap <=`U_DLY GAP_MAX;
    end else begin
        if (gen_reset==RST_ACT_LEVEL)
            cnt_gap <=`U_DLY GAP_MAX;
        else if (gap_expire==1'b0 && time_unit==1'b1)
            cnt_gap <=`U_DLY cnt_gap - 1'd1;
        else
            ;
    end
end
assign gap_expire = (cnt_gap=={GAP_BW{1'b0}}) ? 1'b1 : 1'b0;

//################################################################################
// ASSERTION
//################################################################################

`ifdef CBB_ASSERT_ON
// synopsys translate_off


// synopsys translate_on
`endif

endmodule
